﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using WBFSe3.IO;
using System.IO;
using System.Threading;
using WBFSe3.Formats;
using WBFSe3.Wbfs;
using System.Security;

namespace WBFSe3.IO
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public static class WbfsLoader
    {
        //---------------------------------------------------------------------------------------------------
        // Variables
        //---------------------------------------------------------------------------------------------------
        private static Dictionary<String, WbfsFormat> Formats = new Dictionary<String, WbfsFormat>();


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static WbfsFormat[] GetLoadedFormats()
        {
            return Formats.Values.ToArray();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static WbfsFormat GetFormat(String extension)
        {
            WbfsFormat f;
            if (!Formats.TryGetValue(extension, out f))
                return null;
            else return f;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static void LoadFormatsFromAssembly(String path)
        {
            Assembly assembly = Assembly.LoadFile(path);
            Type[] types = assembly.GetTypes();

            for (int i = 0; i < types.Length; i++)
            {
                if (types[i].BaseType == typeof(WbfsFormat))
                {
                    WbfsFormat format = (WbfsFormat)types[i].
                        GetConstructor(Type.EmptyTypes).Invoke(null);

                    if (!Formats.ContainsKey(format.Extension))
                        Formats.Add(format.Extension, format);
                }
            }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static void LoadBuiltIn()
        {
            Formats.Add(".iso", new WbfsIsoFormat());
            Formats.Add(".wbfs", new WbfsFWbfsFormat());
            Formats.Add("", new WbfsPWbfsFormat());
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static int CountKnownFormats(String[] files)
        {
            int count = 0;
            for (int i = 0; i < files.Length; i++)
            {
                if (String.IsNullOrEmpty(files[i]))
                    continue;

                String ext = Path.GetExtension(files[i]);
                if (String.IsNullOrEmpty(ext)) continue;

                if (Formats.ContainsKey(ext.ToLower()))
                    count++;
            }

            return count;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static WbfsError LoadFile(WbfsPath directory, String filename, out WbfsFileInfo file)
        {
            WbfsError error;
            file = null;

            // Get the drive
            WbfsDriveInfo drive;
            WbfsDriveInfo.GetDrive(directory.Path, out drive);

            // Get the path, extension and the name
            String ext = drive.IsWbfs ? String.Empty :
                Path.GetExtension(filename).ToLower();

            // Typeless files not supported
            if (!drive.IsWbfs && String.IsNullOrEmpty(ext))
                return WbfsError.FormatNotSupported;

            // Get the format descriptor
            WbfsFormat format = WbfsLoader.GetFormat(ext);
            if (format == null) return WbfsError.FormatNotSupported;

            // try Parse
            try
            {
                return ParseWindowsFile(drive, directory, filename, format, out file);
            }
            catch (FileNotFoundException)
            {
                error = WbfsError.IOFileNotFound;
            }
            catch (DirectoryNotFoundException)
            {
                error = WbfsError.IODirNotFound;
            }
            catch (SecurityException)
            {
                error = WbfsError.IOSecurityError;
            }
            catch (SharingViolationException)
            {
                error = WbfsError.IOSharingViolation;
            }
            catch (UnauthorizedAccessException)
            {
                error = WbfsError.IOAccessDenied;
            }
            catch (WbfsIOException exception)
            {
                error = (WbfsError)exception.NativeError;
            }
            catch (IOException)
            {
                error = WbfsError.IOError;
            }
            catch (Exception)
            {
                error = WbfsError.Fail;
            }

            file = null;
            return error;
        }
        
        
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static WbfsError CreateTemporaryFile(String path, String filename,
            WbfsFileInfo source, out WbfsFileInfo file)
        {
            file = null;

            // Get the drive
            WbfsDriveInfo drive;
            WbfsDriveInfo.GetDrive(path, out drive);

            // Get the path, extension and the name
            String ext = drive.IsWbfs ? String.Empty : 
                Path.GetExtension(filename).ToLower();

            // Must have a format unless 
            //it's a wbfs partition file
            if (String.IsNullOrEmpty(ext))
            {
                if (!drive.IsWbfs)
                    return WbfsError.FormatNotSupported;
            }
            else
            {
                if (drive.IsWbfs)
                    return WbfsError.FormatNotSupported;
            }

            // Get the format descriptor
            WbfsFormat format = WbfsLoader.GetFormat(ext);
            if (format == null)
                return WbfsError.FormatNotSupported;

            WbfsFormatCreator creator = format.InvokeCreator();

            // Assert desired sector size and usage table

            try
            {
                int sectorSize = 0;
                WbfsUsage usage;

                if (format.FixedSectorSize)
                {
                    sectorSize = format.DefaultSectorSize;
                }
                else
                {
                    sectorSize = source.SectorSize;
                }

            ASSERTSSIZE:

                if (sectorSize == WbfsConstants.WiiSectorSize)
                {
                    usage = source.WiiSectorUsage;
                }
                else
                {
                    usage = WbfsHelper.CreateWbfsUsage(
                        sectorSize, source.WiiSectorUsage);
                }

                // Initialize the creator

                WbfsError error;
                error = creator.Initialize(drive, path, filename, source, usage, sectorSize, 1);
                if (error != WbfsError.Ok) return error;

                if (creator.SectorSize != sectorSize)
                {
                    sectorSize = creator.SectorSize;
                    goto ASSERTSSIZE;
                }

                file = new WbfsFileInfo(drive, creator, format);
                return WbfsError.Ok;

            }
            catch (FileNotFoundException)
            {
                return WbfsError.IOFileNotFound;
            }
            catch (DirectoryNotFoundException)
            {
                return WbfsError.IODirNotFound;
            }
            catch (SecurityException)
            {
                return WbfsError.IOSecurityError;
            }
            catch (SharingViolationException)
            {
                return WbfsError.IOSharingViolation;
            }
            catch (UnauthorizedAccessException)
            {
                return WbfsError.IOAccessDenied;
            }
            catch (WbfsIOException exception)
            {
                return (WbfsError)exception.NativeError;
            }
            catch (IOException)
            {
                return WbfsError.IOError;
            }
            catch (Exception)
            {
                return WbfsError.Fail;
            }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private static WbfsError ParseWindowsFile(WbfsDriveInfo drive, WbfsPath directory,
            String filename, WbfsFormat format, out WbfsFileInfo file)
        {
            WbfsFormatReader reader;
            WbfsError error;
            String name;
            file = null;

            // Invoke a reader
            reader = format.InvokeReader();

            // Open the file
            name = Path.GetFileName(filename);
            error = reader.Initialize(drive, directory, name);
            if (error != WbfsError.Ok)
            {
                if (error == WbfsError.FormatFixNeeded)
                    error = reader.Fix();

                if (error != WbfsError.Ok)
                    return error;
            }

            // Create the file info
            WbfsFileInfo fi = new WbfsFileInfo(drive, directory, reader, format);
            WbfsFileState fs = fi.State;

            if (fs == WbfsFileState.NotAGame) return WbfsError.FileNotAGame;
            else if (fs == WbfsFileState.Error) return (WbfsError)fi.LastError;

            file = fi;

            // Remove the files from the directory list
            for (int i = 0; i < reader.Path.Count; i++)
            {
                int current = Array.IndexOf(directory.Files,
                    reader.Path.Files[i]);

                if (current >= 0)
                    directory.Files[current] = null;
            }

            return WbfsError.Ok;
        }
    }
}
